/**
	Pulsar engine. Core.
	Additional include file. Base string class

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

namespace pl
{

// Used to String ctor without filling
enum StringStreaming
{
	STRING_STREAMING
};

/**
	Base string class
*/
template<pl_uint32 N> class String
{
private:
	pl_char		_buffer[N];
	pl_uint32	_size;

public:
	// Defailt ctor
	String() :
	_size(0)
	{
		_buffer[0] = 0;
		return;
	}

	// Ctor for placement strings
	explicit String(enum StringStreaming)
	{
		PL_ASSERTE(_size >= 0 && _size < N, PL_T("Bad string initialization"));
		PL_ASSERTE(pl_strlen(_buffer) == _size, PL_T("Bad string initialization"));
		return;
	}

	/// Ctor from char
	explicit String(pl_char c) :
	_size(0)
	{
		set(c);
		return;
	}

	/// Ctor from [first, first + count]
	String(const pl_char* first, pl_uint32 count) :
	_size(0)
	{
		set(first, count);
		return;
	}

	/// Ctor from [first, last]. Last is not included.
	String(const pl_char* first, const pl_char* last) :
	_size(0)
	{
		set(first, last);
		return;
	}

	/// Ctor from const pl_char*
	String(const pl_char* str) :
	_size(0)
	{
		set(str);
		return;
	}

	/// Copy ctor
	String(const String& str) :
	_size(str._size)
	{
		PL_ASSERTE(str._size >= 0 && str._size < N, PL_T("String is not initialized"));
		memcpy(_buffer, str._buffer, (_size + 1) * sizeof(pl_char));
		return;
	}

	/// Copy ctor from different string size
	template<pl_uint32 N2> String(const String<N2>& rhs) :
	_size(0)
	{
		set(rhs.get());
		return;
	}

	/// Assign from char
	String& operator = (pl_char rhs)
	{
		set(rhs);
		return *this;
	}

	/// Assign from string
	String& operator = (const pl_char* rhs)
	{
		set(rhs);
		return *this;
	}

	/// Assign from string
	String& operator = (const String& rhs)
	{
		set(rhs);
		return *this;
	}

	/// set from string with different size
	template<pl_uint32 N2> String& operator = (const String<N2>& rhs)
	{
		set(rhs);
		return *this;
	}

	/// set from pl_char
	void set(pl_char c)
	{
		_buffer[0] = c;
		_buffer[1] = PL_T('\0');
		_size = 1;
		return;
	}

	/// set from [first, first + count]
	void set(const pl_char* first, pl_uint32 count)
	{
		if (count == 0)
		{
			_buffer[0] = PL_T('\0');
			_size = 0;
			return;
		}

		if (first == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("NULL pointer"));
			return;
		}
		if (count > N - 1)
		{
			PL_ASSERTW(FALSE, PL_T("Buffer overflow"));
			count = N - 1;
		}
		memcpy(_buffer, first, count * sizeof(pl_char));
		_buffer[count] = PL_T('\0');
		_size = count;
		return;
	}

	/// set from [first, last]. Last is not included.
	void set(const pl_char* first, const pl_char* last)
	{
		PL_ASSERTW(first <= last, PL_T("Invalid parameters"));
		set(first, static_cast<pl_uint32>(last - first));
		return;
	}

	/// set from const pl_char*
	void set(const pl_char* str)
	{
		if (str == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("NULL pointer"));
			return;
		}
		set(str, pl_strlen(str));
		return;
	}

	/// set from another string with different size
	template<pl_uint32 N2> void set(const String<N2>& rhs)
	{
		if (rhs.size() > N - 1)
		{
			PL_ASSERTW(FALSE, PL_T("right string too long"));
			memcpy(_buffer, rhs._buffer, sizeof(pl_char) * (N - 1));
			_buffer[N-1] = PL_T('\0');
			return;
		}
		_size = rhs.size();
		memcpy(_buffer, rhs.get(), (_size + 1) * sizeof(pl_char));
		return;
	}

	/// Set from const pl_char*, allow NULL pointer
	void setSafe(const pl_char* str)
	{
		if (str == NULL)
		{
			clear();
		}
		else
		{
			set(str);
		}
		return;
	}

	/// set from another string
	void set(const String& str)
	{
		PL_ASSERTE(str._size >= 0 && str._size < N, PL_T("String is not initialized"));
		_size = str._size;
		memcpy(_buffer, str._buffer, (_size + 1) * sizeof(pl_char));
		return;
	}

	/// Contatenate using += (char)
	String& operator += (pl_char rhs)
	{
		return append(rhs);
	}

	/// Contatenate using += (string)
	String& operator += (const pl_char* rhs)
	{
		return append(rhs);
	}

	/// Contatenate using += (string)
	String& operator += (const String& rhs)
	{
		return append(rhs);
	}

	/// Contatenate using += (string with different size)
	template<pl_uint32 N2> String& operator += (const String<N2>& rhs)
	{
		return append(rhs.get());
	}

	/// Concatenate with char
	String& append(pl_char c)
	{
		if (_size >= N - 1)
		{
			PL_ASSERTW(FALSE, PL_T("Buffer overflow"));
			return *this;
		}
		_buffer[_size] = c;
		++_size;
		_buffer[_size] = PL_T('\0');
		return *this;
	}

	/// Concatenate with [first, first + count]
	String& append(const pl_char* first, pl_uint32 count)
	{
		if (first == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("NULL pointer"));
			return *this;
		}
		if (_size + count > N - 1)
		{
			PL_ASSERTW(FALSE, PL_T("Buffer overflow"));
			count = N - _size - 1;
		}
		if (count > 0)
		{
			memcpy(&_buffer[_size], first, count * sizeof(pl_char));
			_size += count;
			_buffer[_size] = PL_T('\0');
		}
		return *this;
	}

	/// Concatenate with [first, last]. Last is not included
	String& append(const pl_char* first, const pl_char* last)
	{
		PL_ASSERTW(first <= last, PL_T("Invalid parameters"));
		return append(first, static_cast<pl_uint32>(last - first));
	}

	/// Concatenate with string
	String& append(const pl_char* str)
	{
		return append(str, pl_strlen(str));
	}

	/// Concatenate with string
	String& append(const String& str)
	{
		return append(str.get(), str.size());
	}

	/// Concatenate with string with different size
	template<pl_uint32 N2> String& append(const String<N2>& str)
	{
		return append(str.get(), str.size());
	}

	/// Insert string before offset
	String& insert(pl_uint32 offset, const String& right)
	{
		return insert(offset, right, 0, right.size());
	}

	/// Insert string [right_offset, right_offset + right_count) before offset
	String& insert(pl_uint32 offset, const String& right, pl_uint32 right_offset, pl_uint32 right_count)
	{
		if (right_offset > right.size() || right_offset + right_count > right.size())
		{
			PL_ASSERTW(FALSE, PL_T("Right offset outside string data"));
			return *this;
		}
		return insert(offset, right.get() + right_offset, right_count);
	}

	/// Insert [first, first + count] before offset
	String& insert(pl_uint32 offset, const pl_char* first, pl_uint32 count)
	{
		if (first >= _buffer && first < &_buffer[_size])
		{
			PL_ASSERTW(FALSE, PL_T("Input String overlaps buffer"));
		}
		if (offset > _size)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong insert position"));
			return *this;
		}
		if (_size + count > N - 1)
		{
			PL_ASSERTW(FALSE, PL_T("Buffer overflow"));
			count = N - _size - 1;
		}
		if (count > 0)
		{
			memmove(&_buffer[offset + count], &_buffer[offset], (_size - offset + 1) * sizeof(pl_char));
			memcpy(&_buffer[offset], first, count * sizeof(pl_char));
			_size += count;
		}
		return *this;
	}

	/// Replace [offset, offset + count] with right
	String& replace(pl_uint32 offset, pl_uint32 count, const String& right)
	{
		return replace(offset, count, right, 0, right.size());
	}

	/// Replace [offset, offset + count] with right [right_offset, right_offset + right_count]
	String& replace(pl_uint32 offset, pl_uint32 count, const String& right, pl_uint32 right_offset, pl_uint32 right_count)
	{
		return replace(offset, count, right.get() + right_offset, right_count);
	}

	/// Replace [offset, offset + count] with [first, right + count]
	String& replace(pl_uint32 offset, pl_uint32 count, const pl_char* first, pl_uint32 right_count)
	{
		if (first >= _buffer && first < &_buffer[_size])
		{
			PL_ASSERTW(FALSE, PL_T("Input String overlaps buffer"));
		}
		if (offset > _size)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong replace position"));
			return *this;
		}
		if (right_count <= count)
		{
			memcpy(&_buffer[offset], first, right_count * sizeof(pl_char));
			memmove(&_buffer[offset + right_count], &_buffer[offset + count], (_size - offset - count + 1) * sizeof(pl_char));
			_size -= count - right_count;
		}
		else
		{
			const pl_uint32 diff = right_count - count;
			if (_size + diff > N - 1)
			{
				PL_ASSERTW(FALSE, PL_T("Buffer overflow"));
				return *this;
			}
			memmove(&_buffer[offset + right_count], &_buffer[offset + count], (_size - offset - count + 1) * sizeof(pl_char));
			memcpy(&_buffer[offset], first, right_count * sizeof(pl_char));
			_size += diff;
		}
		return *this;
	}

	/// Erase character by index
	String& erase(pl_uint32 pos)
	{
		PL_ASSERTW(pos < _size, PL_T("Wrong erase position"));
		erase(pos, 1);
		return *this;
	}

	/// Erase characters in range [offset, offset + count]
	String& erase(pl_uint32 offset, pl_uint32 count)
	{
		if (offset >= _size)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong erase position"));
			return *this;
		}
		if (offset + count > _size)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong erase range"));
			count = _size - offset;
		}
		if (count > 0)
		{
			memmove(&_buffer[offset], &_buffer[offset + count], (_size - (offset + count - 1)) * sizeof(pl_char));
			_size -= count;
		}
		return *this;
	}

	/// Truncate a string
	inline void truncate(pl_uint32 pos)
	{
		if (pos > _size)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong truncate position"));
			return;
		}
		_size = pos;
		_buffer[_size] = PL_T('\0');
		return;
	}

	/// Clear the string
	inline void clear()
	{
		_size = 0;
		_buffer[0] = PL_T('\0');
		return;
	}

	/// Replace 'from' to 'to'
	inline void replaceChar(pl_char from, pl_char to)
	{
		for (pl_uint32  i=0; i<_size; i++)
		{
			if (_buffer[i] == from)
			{
				_buffer[i] = to;
			}
		}
		return;
	}

	/// Get a reference to a char
	pl_char& operator[](pl_uint32 index)
	{
		PL_ASSERTE(index <= _size, PL_T("Index out of range"));
		return _buffer[index];
	}

	/// Get a const reference to a char
	const pl_char& operator[](pl_uint32 index) const
	{
		PL_ASSERTE(index <= _size, PL_T("Index out of range"));
		return _buffer[index];
	}

	/// Get string size (without last \0)
	pl_uint32 size() const
	{
		return _size;
	}

	/// Get string size in bytes (without last \0)
	pl_uint32 sizeBytes() const
	{
		return _size*sizeof(pl_char);
	}

	/// Get sizeof of char
	pl_uint32 sizeChar() const
	{
		return sizeof(pl_char);
	}

	/// Check is the string empty
	bool empty() const
	{
		return _size == 0;
	}

	/// Get constant pointer to string
	const pl_char* get() const
	{
		return _buffer;
	}

	/// Get constant pointer to string (+ offset)
	const pl_char* get(pl_uint32 offset) const
	{
		if (offset > size())
		{
			PL_ASSERTW(FALSE, PL_T("Index out of range"));
			return PL_T("");
		}
		return get() + offset;
	}

	/// Get maximum string capacity in chars
	inline static pl_uint32 capacity()
	{
		return N;
	}

	/// Find a char
	inline pl_uint32 find(pl_char c, pl_uint32 startPos = 0) const
	{
		PL_ASSERTE(startPos <= _size, PL_T("Wrong data"));
		for (pl_uint32 i=startPos; i<_size; i++)
		{
			if (_buffer[i] == c)
			{
				return i;
			}
		}
		return (pl_uint32)-1;
	}

	/// Reverse find a char
	inline pl_uint32 reverseFind(pl_char c) const
	{
		pl_uint32 i = _size;

		do
		{
			if (_buffer[i] == c)
			{
				return i;
			}
		} while ((i--) != 0);
		return (pl_uint32)-1;
	}

	/// Find substring
	inline pl_uint32 findString(const pl_char* substring, pl_uint32 startPos = 0) const
	{
		if (substring == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong arguments"));
			return (pl_uint32)-1;
		}

		PL_ASSERTW(startPos <= _size, PL_T("Index out of range"));

		const pl_char* p = pl_strstr((pl_char*)_buffer + startPos, substring);
		if (p == NULL)
		{
			return (pl_uint32)-1;
		}
		return (pl_uint32)(pl_intptr)(p - _buffer);
	}

	/// Check is string begin with sequence
	bool isBegin(const pl_char* str) const
	{
		if (str == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong arguments"));
			return false;
		}
		pl_uint32 len = pl_strlen(str);

		if (len > _size || len == 0)
		{
			return false;
		}
		return pl_strncmp(_buffer, str, len) == 0;
	}

	/// Check is string end with sequence
	bool isEnd(const pl_char* str) const
	{
		if (str == NULL)
		{
			PL_ASSERTW(FALSE, PL_T("Wrong arguments"));
			return false;
		}
		pl_uint32 len = pl_strlen(str);

		if (len > _size || len == 0)
		{
			return false;
		}
		return pl_strncmp(_buffer + _size - len, str, len) == 0;
	}

	/// Remove whitespaces from right
	const String& trimLeft()
	{
		pl_uint32 i;
		if (empty())
		{
			return *this;
		}
		for (i=0; i<_size; i++)
		{
			if (!pl_isspace(_buffer[i]))
			{
				break;
			}
		}
		if (i > 0)
		{
			erase(0, i);
		}
		return *this;
	}

	/// Remove whitespace from right
	const String& trimRight()
	{
		if (empty())
		{
			return *this;
		}
		pl_uint32 i = _size;
		while (true)
		{
			if (!pl_isspace(_buffer[--i]))
			{
				truncate(i+1);
				break;
			}
			if (i == 0)
			{
				clear(); // all chars are spaces
				break;
			}
		}
		return *this;
	}

	/// Removes whitespace
	const String& trim()
	{
		trimLeft();
		trimRight();
		return *this;
	}

	/// Remove char 'c' from left
	const String& trimLeft(pl_char c)
	{
		if (empty())
		{
			return *this;
		}
		pl_uint32 i;
		for (i=0; i<_size; i++)
		{
			if (_buffer[i] != c)
			{
				break;
			}
		}
		if (i > 0)
		{
			erase(0, i);
		}
		return *this;
	}

	/// Remove char 'c' from right
	const String& trimRight(pl_char c)
	{
		if (empty())
		{
			return *this;
		}
		pl_uint32 i = _size;
		while (true)
		{
			if (_buffer[--i] != c)
			{
				truncate(i+1);
				break;
			}
			if (i == 0)
			{
				clear();
				break;
			}
		}
		return *this;
	}

	/// Remove char from all sides
	const String& trim(pl_char c)
	{
		trimLeft(c);
		trimRight(c);
		return *this;
	}

	/// Fill string from left side with specified symbol
	const String& fillLeft(pl_char c, pl_uint32 count)
	{
		if (_size + count >= N)
		{
			PL_ASSERTE(FALSE, PL_T("Bad arguments"));
			return *this;
		}
		if (_size < count)
		{
			// Move chars
			pl_uint32 moveTo = count;
			pl_uint32 moveFrom = _size;
			while (moveFrom > 0)
			{
				_buffer[moveTo--] = _buffer[moveFrom--];
			}
			_buffer[moveTo--] = _buffer[moveFrom]; // Copy last char
			for (pl_uint32 i = 0; i < moveTo; i++)
			{
				_buffer[i] = c; // fill c
			}
			_size = count;
		}
		return *this;
	}

	/// Fill string from right side with specified symbol
	const String& fillRight(pl_char c, pl_uint32 count)
	{
		if (_size + count >= N)
		{
			PL_ASSERTE(FALSE, PL_T("Bad arguments"));
			return *this;
		}
		while (_size < count)
		{
			_buffer[_size++] = c;
		}
		_buffer[_size] = PL_T('\0');
		return *this;
	}

	/// make string uppercase
	const String& uppercase()
	{
		pl_uint32 i;
		for (i=0; i<_size; i++)
		{
			_buffer[i] = pl_toupper(_buffer[i]);
		}
		return *this;
	}

	/// make string lowercase
	const String& lowercase()
	{
		pl_uint32 i;
		for (i=0; i<_size; i++)
		{
			_buffer[i] = pl_tolower(_buffer[i]);
		}
		return *this;
	}
};

//
//Additional operations
//
// Concatenate with char from left
template<pl_uint32 N> String<N> operator + (pl_char lhs, const String<N>& rhs)
{
	return String<N>(lhs) += rhs;
}

// Concatenate with string with another size from left
template<pl_uint32 N> String<N> operator + (const String<N>& lhs, pl_char rhs)
{
	return String<N>(lhs) += rhs;
}

// Concatenate with string from left
template<pl_uint32 N> String<N> operator + (const pl_char* lhs, const String<N>& rhs)
{
	return String<N>(lhs) += rhs;
}

// Concatenate with string with another size from left
template<pl_uint32 N> String<N> operator + (const String<N>& lhs, const pl_char* rhs)
{
	return String<N>(lhs) += rhs;
}

// Concatenate two different strings
template<pl_uint32 N> String<N> operator + (const String<N>& lhs, const String<N>& rhs)
{
	return String<N>(lhs) += rhs;
}

// Equal
template<pl_uint32 N> bool operator == (const pl_char* lhs, const String<N>& rhs)
{
	pl_uint32 lhs_size = pl_strlen(lhs);
	if (lhs_size != rhs.size())
	{
		return false;
	}
	return memcmp(lhs, rhs.get(), lhs_size * sizeof(pl_char)) == 0;
}

// Equal (with char*)
template<pl_uint32 N> bool operator == (const String<N>& lhs, const pl_char* rhs)
{
	return rhs == lhs;
}

// Equal (with another size string)
template<pl_uint32 N, pl_uint32 N2> bool operator == (const String<N>& lhs, const String<N2>& rhs)
{
	if (lhs.size() != rhs.size())
	{
		return false;
	}
	return memcmp(lhs.get(), rhs.get(), lhs.sizeBytes()) == 0;
}

// Not Equal (with char*)
template<pl_uint32 N> bool operator != (const pl_char* lhs, const String<N>& rhs)
{
	return !(lhs == rhs);
}

// Not Equal (with string)
template<pl_uint32 N> bool operator != (const String<N>& lhs, const pl_char* rhs)
{
	return !(lhs == rhs);
}

// Not Equal (with another size string)
template<pl_uint32 N, pl_uint32 N2> bool operator != (const String<N>& lhs, const String<N2>& rhs)
{
	return !(lhs == rhs);
}

// Less
template<pl_uint32 N> bool operator < (const pl_char* lhs, const String<N>& rhs)
{
	return pl_strcmp(lhs, rhs.get()) < 0;
}

template<pl_uint32 N> bool operator < (const String<N>& lhs, const pl_char* rhs)
{
	return pl_strcmp(lhs.get(), rhs) < 0;
}

template<pl_uint32 N, pl_uint32 N2> bool operator < (const String<N>& lhs, const String<N2>& rhs)
{
	return pl_strcmp(lhs.get(), rhs.get()) < 0;
}

// Great
template<pl_uint32 N> bool operator > (const pl_char* lhs, const String<N>& rhs)
{
	return rhs < lhs;
}

template<pl_uint32 N> bool operator > (const String<N>& lhs, const pl_char* rhs)
{
	return rhs < lhs;
}

template<pl_uint32 N, pl_uint32 N2> bool operator > (const String<N>& lhs, const String<N2>& rhs)
{
	return rhs < lhs;
}

// Less or equal
template<pl_uint32 N> bool operator <= (const pl_char* lhs, const String<N>& rhs)
{
	return !(rhs < lhs);
}

template<pl_uint32 N> bool operator <= (const String<N>& lhs, const pl_char* rhs)
{
	return !(rhs < lhs);
}

template<pl_uint32 N, pl_uint32 N2> bool operator <= (const String<N>& lhs, const String<N2>& rhs)
{
	return !(rhs < lhs);
}

// Great or equal
template<pl_uint32 N> bool operator >= (const pl_char* lhs, const String<N>& rhs)
{
	return !(lhs < rhs);
}

template<pl_uint32 N> bool operator >= (const String<N>& lhs, const pl_char* rhs)
{
	return !(lhs < rhs);
}

template<pl_uint32 N, pl_uint32 N2> bool operator >= (const String<N>& lhs, const String<N2>& rhs)
{
	return !(lhs < rhs);
}

// Standard string types
typedef String<32> String32;
typedef String<64> String64;
typedef String<128> String128;
typedef String<256> String256;

} // namespace pl

//
// End of file 'pulsar_string.h'
//
